/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 - 2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.form.editor.model.visitor;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.constants.client.Charset;
import com.genia.toolbox.web.gwt.basics.client.i18n.GwtI18nMessage;
import com.genia.toolbox.web.gwt.form.client.form.AbstractDateItem;
import com.genia.toolbox.web.gwt.form.client.form.AbstractPeriodItem;
import com.genia.toolbox.web.gwt.form.client.form.BasicForm;
import com.genia.toolbox.web.gwt.form.client.form.ButtonItem;
import com.genia.toolbox.web.gwt.form.client.form.CheckBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.DateItem;
import com.genia.toolbox.web.gwt.form.client.form.DateTimeItem;
import com.genia.toolbox.web.gwt.form.client.form.DynamicListBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.DynamicRadioButtonItem;
import com.genia.toolbox.web.gwt.form.client.form.EnumDescriptableValuedItemEntryList;
import com.genia.toolbox.web.gwt.form.client.form.FileUploadItem;
import com.genia.toolbox.web.gwt.form.client.form.Form;
import com.genia.toolbox.web.gwt.form.client.form.HorizontalForm;
import com.genia.toolbox.web.gwt.form.client.form.I18nTextAreaItem;
import com.genia.toolbox.web.gwt.form.client.form.I18nTextBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.ImageItem;
import com.genia.toolbox.web.gwt.form.client.form.InlineFormItem;
import com.genia.toolbox.web.gwt.form.client.form.InlineFormMultipleItem;
import com.genia.toolbox.web.gwt.form.client.form.InlineFormSimpleItem;
import com.genia.toolbox.web.gwt.form.client.form.Item;
import com.genia.toolbox.web.gwt.form.client.form.LabelItem;
import com.genia.toolbox.web.gwt.form.client.form.LabeledForm;
import com.genia.toolbox.web.gwt.form.client.form.ListBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.MultipleFileUploadItem;
import com.genia.toolbox.web.gwt.form.client.form.PasswordBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.RadioButtonItem;
import com.genia.toolbox.web.gwt.form.client.form.RichTextAreaItem;
import com.genia.toolbox.web.gwt.form.client.form.SimpleItem;
import com.genia.toolbox.web.gwt.form.client.form.SimpleValuedItemEntryList;
import com.genia.toolbox.web.gwt.form.client.form.SubFormMultipleItem;
import com.genia.toolbox.web.gwt.form.client.form.SubFormPolicy;
import com.genia.toolbox.web.gwt.form.client.form.SubFormSimpleItem;
import com.genia.toolbox.web.gwt.form.client.form.TabForm;
import com.genia.toolbox.web.gwt.form.client.form.TextAreaItem;
import com.genia.toolbox.web.gwt.form.client.form.TextBoxItem;
import com.genia.toolbox.web.gwt.form.client.form.TimeItem;
import com.genia.toolbox.web.gwt.form.client.form.ValuedItemEntry;
import com.genia.toolbox.web.gwt.form.client.form.ValuedItemEntryList;
import com.genia.toolbox.web.gwt.form.client.form.VerticalForm;
import com.genia.toolbox.web.gwt.form.client.validator.Validator;
import com.genia.toolbox.web.gwt.form.client.visitor.FormVisitor;
import com.genia.toolbox.web.gwt.form.server.spring.factory.EnumDescriptableValuedItemEntryListFactory;

/**
 * this class generates xml which represents a 1gtoolbox form understable by
 * spring.
 */
public class SpringWriterFormVisitor
    implements FormVisitor
{

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String SAVE_PROPERTY = "save";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String ACTION_PROPERTY = "action";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String VALIDATOR_PROPERTY = "validator";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String SUB_FORM_POLICY_PROPERTY = "subFormPolicy";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String SUB_FORM_IDENTIFIER_PROPERTY = "subFormIdentifier";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String VERTICAL_DISPLAY_PROPERTY = "verticalDisplay";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String ENTRIES_PROPERTY = "entries";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String MULTIPLE_SELECT_PROPERTY = "multipleSelect";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String VISIBLE_ITEM_COUNT_PROPERTY = "visibleItemCount";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String URL_PROPERTY = "url";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String ENABLED_PROPERTY = "enabled";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String LABEL_KEY_PROPERTY = "labelKey";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String NAME_PROPERTY = "name";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String SORT_BUTTONS_DISPOSITION_VERTICAL_PROPERTY = "sortButtonsDispositionVertical";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String SORTABLE_PROPERTY = "sortable";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String NULLABLE_PROPERTY = "nullable";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String DATE_FORMAT_PROPERTY = "dateFormat";

  /**
   * the name of the property of some {@link Form}.
   */
  private static final String INLINE_FORM_IDENTIFIER_PROPERTY = "inlineFormIdentifier";

  /**
   * Logger for the class.
   */
  private static final Logger LOGGER = LoggerFactory.getLogger(SpringWriterFormVisitor.class);

  /**
   * Exception which occured during process, if any.
   */
  private Exception exception = null;

  /**
   * the spring config file prefix.
   */
  private static final String PREFIX = "<?xml version=\"1.0\" encoding=\""
      + Charset.UTF8
      + "\"?>\n\t<beans xmlns=\"http://www.springframework.org/schema/beans\"\n\t\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t\txsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd\">\n";

  /**
   * the spring config file suffix.
   */
  private static final String SUFFIX = "</beans>";

  /**
   * the <code>Appendable</code> to which the config file is written.
   */
  private final transient Appendable appendable;

  /**
   * the name of the spring bean describing the portlet being converted.
   */
  private final transient String name;

  /**
   * Whether the first form is begin written or not.
   */
  private transient boolean first = true;

  /**
   * whether the bean to save is a prototype or not.
   */
  private transient final boolean isPrototype;



  /**
   * return the {@link Exception} which occured during the process, or
   * <code>null</code> if the process was a success.
   * 
   * @return exception which occured during process
   */
  public Exception getException()
  {
    return exception;
  }



  /**
   * visit a {@link InlineFormItem}.
   * 
   * @param inlineFormItem
   *          the {@link InlineFormItem} to visit
   */
  private void visitInlineFormItem(InlineFormItem inlineFormItem, String... properties)
  {
    visitItem(inlineFormItem, appendProperties(properties, INLINE_FORM_IDENTIFIER_PROPERTY));
  }



  /**
   * visit a {@link InlineFormSimpleItem}.
   * 
   * @param inlineFormSimpleItem
   *          the {@link InlineFormSimpleItem} to visit
   */
  public void visitInlineFormSimpleItem(InlineFormSimpleItem inlineFormSimpleItem)
  {
    visitInlineFormItem(inlineFormSimpleItem, NULLABLE_PROPERTY);
  }



  /**
   * visit a {@link InlineFormMultipleItem}.
   * 
   * @param inlineFormMultipleItem
   *          the {@link InlineFormMultipleItem} to visit
   */
  public void visitInlineFormMultipleItem(InlineFormMultipleItem inlineFormMultipleItem)
  {
    visitInlineFormItem(inlineFormMultipleItem, SORTABLE_PROPERTY, SORT_BUTTONS_DISPOSITION_VERTICAL_PROPERTY);
  }



  /**
   * constructor.
   * 
   * @param appendable
   *          the <code>Appendable</code> to which the config file is written
   * @param name
   *          the name of the spring bean describing the portlet being converted
   * @param isPrototype
   *          whether the bean to save is a prototype or not
   */
  public SpringWriterFormVisitor(final Appendable appendable, final String name, final boolean isPrototype)
  {
    this.appendable = appendable;
    this.name = name;
    this.isPrototype = isPrototype;
  }



  /**
   * append the <code>CharSequence</code> to the <code>Appendable</code>.
   * 
   * @param charSequence
   *          the <code>CharSequence</code> begin appended.
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void append(final CharSequence charSequence)
      throws TechnicalIOException
  {
    try {
      appendable.append(charSequence);
    }
    catch (final IOException e) {
      throw new TechnicalIOException(e);
    }
  }



  /**
   * append a CDATA value to the result.
   * 
   * @param cdata
   *          the CDATA to append
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void appendCDATA(final String cdata)
      throws TechnicalIOException
  {
    append("<![CDATA[");
    append(cdata);
    append("]]>");
  }



  /**
   * append a property to the result.
   * 
   * @param name
   *          the name of the property
   * @param value
   *          the value of the property
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void appendProperty(final String name, final String value)
      throws TechnicalIOException
  {
    if (value != null) {
      append("<property name='");
      append(name);
      append("'><value>");
      appendCDATA(value);
      append("</value></property>");
    }
  }



  /**
   * append the prefix xml fragment common to all <code>Object</code>. It also
   * append the xml prefix if the object being converted is the first one.
   * 
   * @param clazz
   *          the form being converted
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void prefixForObject(final Class<?> clazz)
      throws TechnicalIOException
  {

    prefixForObject(clazz.getCanonicalName(), (Object) null);

  }



  /**
   * append the prefix xml fragment common to all <code>Object</code>. It also
   * append the xml prefix if the object being converted is the first one.
   * 
   * @param obj
   *          the form being converted
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void prefixForObject(final Object obj, String... properties)
      throws TechnicalIOException
  {

    prefixForObject(obj.getClass().getCanonicalName(), obj, properties);

  }



  /**
   * append the prefix xml fragment common to all <code>Form</code>. It also
   * append the xml prefix if the form being converted is the first one.
   * 
   * @param className
   *          of the bean to convert
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void prefixForObject(final String className, Object object, String... properties)
  {
    try {

      if (first) {
        append(PREFIX);
      }
      append("<bean ");
      if (first) {
        append("name='");
        append(name);
        append("' ");
        if (isPrototype) {
          append("scope='prototype' ");
        }

        this.first = false;
      }
      append("class='");
      append(className);
      append("'>");
      if (object != null) {
        for (String property : properties) {
          Object value = PropertyUtils.getProperty(object, property);
          if (value != null) {
            if (value instanceof GwtI18nMessage) {
              appendLabelProperty(property, (GwtI18nMessage) value);
            }
            else if (value instanceof Validator) {
              appendValidator(property, (Validator) value);
            }
            else if (value instanceof SubFormPolicy) {
              appendSubFormPolicyProperty(property, (SubFormPolicy) value);

            }
            else if (value instanceof ValuedItemEntryList) {
              appendValuedItemEntryListProperty(property, (ValuedItemEntryList) value);
            }
            else {
              appendProperty(property, String.valueOf(value));
            }
          }
        }
      }
    }
    catch (Exception e) {
      exception = e;
    }
  }



  /**
   * append the prefix xml fragment common to all <code>Form</code>. It also
   * append the xml prefix if the form being converted is the first one.
   * 
   * @param form
   *          the form being converted
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void prefixForForm(final Form form, String... properties)
      throws TechnicalIOException
  {
    prefixForObject(form, appendProperties(properties, NAME_PROPERTY));
  }



  private String[] appendProperties(String[] baseProperties, String... properties)
  {
    if (baseProperties.length == 0) {
      return properties;
    }
    if (properties.length == 0) {
      return baseProperties;
    }
    String[] result = new String[baseProperties.length + properties.length];
    System.arraycopy(baseProperties, 0, result, 0, baseProperties.length);
    System.arraycopy(properties, 0, result, baseProperties.length, properties.length);
    return result;
  }



  /**
   * append the suffix xml fragment common to all <code>Form</code>. It also
   * append the xml suffix if the form being converted is the first one.
   * 
   * @param first
   *          is the portlet being converted the first one
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void suffixForForm(final boolean first)
      throws TechnicalIOException
  {
    append("</bean>");

    if (first) {
      append(SUFFIX);
    }
  }



  /**
   * append a GwtI18nMessage label to the xml.
   * 
   * @param propertyName
   *          to set
   * @param label
   *          to append
   * @throws TechnicalIOException
   *           if an error occured
   */
  private void appendLabelProperty(String propertyName, GwtI18nMessage label)
      throws TechnicalIOException
  {

    if (label != null) {
      append("<property name='");
      append(propertyName);
      append("'>");
      append("<bean class='");
      append(label.getClass().getCanonicalName());
      append("'>");

      appendProperty("key", label.getKey());
      String[] parameters = label.getParameters();
      if (parameters.length != 0) {

        append("<property name='parameters'><list>");

        for (int i = 0; i < parameters.length; ++i) {

          append("<value>");
          appendCDATA(parameters[i]);
          append("</value>");

        }

        append("</list></property>");
      }

      append("</bean></property>");
    }

  }



  /**
   * Append Validator.
   * 
   * @param validator
   *          used
   * @throws TechnicalIOException
   *           if any
   */
  @SuppressWarnings("unchecked")
  private void appendValidator(String property, Validator validator)
      throws TechnicalIOException
  {
    append("<property name='");
    append(property);
    append("'>");

    Map<String, Object> properties = null;
    try {
      properties = PropertyUtils.describe(validator);
      for (Iterator<Entry<String, Object>> iterator = properties.entrySet().iterator(); iterator.hasNext();) {
        Entry<String, Object> entry = iterator.next();
        if (!PropertyUtils.isWriteable(validator, entry.getKey())) {
          iterator.remove();
        }
      }
    }
    catch (Exception e) {
      LOGGER.error(e.getMessage(), e);
    }
    append("<bean class='");
    append(validator.getClass().getCanonicalName());
    append("'>");
    if (properties != null && !properties.isEmpty()) {
      for (Entry<String, Object> entry : properties.entrySet()) {
        PropertyEditor propertyEditor = null;
        try {
          propertyEditor = PropertyEditorManager.findEditor(PropertyUtils.getPropertyType(validator, entry.getKey()));
        }
        catch (Exception e) {
          LOGGER.error(e.getMessage(), e);
        }
        String value = null;
        if (propertyEditor != null) {
          propertyEditor.setValue(entry.getValue());
          value = propertyEditor.getAsText();
        }
        else {
          value = entry.getValue().toString();
        }
        appendProperty(entry.getKey(), value);
      }
    }
    append("</bean>");
    append("</property>");
  }



  /**
   * append the prefix xml fragment common to all {@link Item}.
   * 
   * @param item
   *          the item being converted
   * @throws TechnicalIOException
   *           if an error occurred
   */
  private void prefixForItem(final Item item, String... properties)
      throws TechnicalIOException
  {

    prefixForForm(item, appendProperties(properties, LABEL_KEY_PROPERTY, ENABLED_PROPERTY));
  }



  /**
   * append the prefix xml fragment common to all <code>BasicForm</code>.
   * 
   * @param basicForm
   *          the basicForm being converted
   * @throws TechnicalIOException
   *           if an error occurred
   */
  @SuppressWarnings("unchecked")
  private void prefixForBasicForm(BasicForm basicForm)
      throws TechnicalIOException
  {

    prefixForForm(basicForm);
    if (basicForm.getForms() != null && !basicForm.getForms().isEmpty()) {
      append("<property name='forms'><list>");
      for (final Iterator<Form> it = basicForm.getForms().iterator(); it.hasNext();) {
        it.next().accept(this);
      }
      append("</list></property>");
    }
  }



  /**
   * append a GwtI18nMessage label to the xml.
   * 
   * @param propertyName
   *          to set
   * @param label
   *          to append
   * @throws TechnicalIOException
   *           if an error occured
   */
  private void appendSubFormPolicyProperty(String propertyName, SubFormPolicy policy)
      throws TechnicalIOException
  {
    append("<property name='");
    append(propertyName);
    append("'>");
    append("<bean class='");
    append(policy.getClass().getCanonicalName());
    append("'>");

    appendProperty("canCreate", String.valueOf(policy.isCanCreate()));
    appendProperty("canUpdate", String.valueOf(policy.isCanUpdate()));
    appendProperty("showDisabled", String.valueOf(policy.isShowDisabled()));

    append("</bean></property>");
  }



  /**
   * append a GwtI18nMessage label to the xml.
   * 
   * @param propertyName
   *          to set
   * @param label
   *          to append
   * @throws TechnicalIOException
   *           if an error occured
   */
  private void appendValuedItemEntryListProperty(String propertyName, ValuedItemEntryList valuedItemEntryList)
      throws TechnicalIOException
  {
    append("<property name='");
    append(propertyName);
    append("'>");
    valuedItemEntryList.accept(this);
    append("</property>");
  }



  /**
   * generate checkBoxItem from a CheckBox widget.
   * 
   * @param checkBoxItem
   *          an object CheckBoxItem.
   */
  public void visitCheckBoxItem(final CheckBoxItem checkBoxItem)
  {
    visitSimpleItem(checkBoxItem);
  }



  /**
   * create a new widget from a groupForm.
   * 
   * @param horizontalForm
   *          is of type GroupForm. contains a list of Forms.
   */

  public void visitHorizontalForm(final HorizontalForm horizontalForm)
  {

    if (exception != null) {
      return;
    }

    try {
      final boolean first = this.first;
      prefixForBasicForm(horizontalForm);
      suffixForForm(first);
    }
    catch (TechnicalIOException tioe) {
      exception = tioe;
    }

  }



  /**
   * this method implement ImageItem from widget Image'gwt.
   * 
   * @param imageItem
   *          is of type ImageItem.
   */
  public void visitImageItem(final ImageItem imageItem)
  {
    visitSimpleItem(imageItem, URL_PROPERTY);
  }



  /**
   * visit a {@link DynamicListBoxItem}.
   * 
   * @param item
   *          the {@link DynamicListBoxItem} to visit
   */
  public void visitDynamicListBoxItem(DynamicListBoxItem item)
  {
    visitSimpleItem(item, VISIBLE_ITEM_COUNT_PROPERTY, MULTIPLE_SELECT_PROPERTY);
  }



  /**
   * visit a {@link ListBoxItem}.
   * 
   * @param listBoxItem
   *          is a listbox.
   */
  public void visitListBoxItem(final ListBoxItem listBoxItem)
  {
    visitSimpleItem(listBoxItem, ENTRIES_PROPERTY, VISIBLE_ITEM_COUNT_PROPERTY, MULTIPLE_SELECT_PROPERTY);
  }



  /**
   * Generate RadioButtonItem from RadioButton widget.
   * 
   * @param radioButtonItem
   *          is of type RadioButton.
   */
  public void visitRadioButtonItem(final RadioButtonItem radioButtonItem)
  {
    visitSimpleItem(radioButtonItem, ENTRIES_PROPERTY, VERTICAL_DISPLAY_PROPERTY);
  }



  /**
   * visit a {@link DynamicRadioButtonItem}.
   * 
   * @param item
   *          the {@link DynamicRadioButtonItem} to visit
   */
  public void visitDynamicRadioButtonItem(DynamicRadioButtonItem item)
  {
    visitSimpleItem(item, VERTICAL_DISPLAY_PROPERTY);
  }



  /**
   * visit {@link SubFormMultipleItem}.
   * 
   * @param item
   *          the item to visit
   */
  public void visitSubFormMultipleItem(SubFormMultipleItem item)
  {
    visitSimpleItem(item, SUB_FORM_IDENTIFIER_PROPERTY, SUB_FORM_POLICY_PROPERTY, SORTABLE_PROPERTY, SORT_BUTTONS_DISPOSITION_VERTICAL_PROPERTY);
  }



  /**
   * visit {@link SubFormSimpleItem}.
   * 
   * @param item
   *          the item to visit
   */
  public void visitSubFormSimpleItem(final SubFormSimpleItem item)
  {
    visitSimpleItem(item, SUB_FORM_IDENTIFIER_PROPERTY, SUB_FORM_POLICY_PROPERTY);
  }



  /**
   * create a form from tabForm.
   * 
   * @param tabForm
   *          is of type TabForm.He is composed of Forms.
   */
  public void visitTabForm(final TabForm tabForm)
  {

    if (exception != null) {
      return;
    }

    try {

      final boolean first = this.first;
      prefixForBasicForm(tabForm);

      suffixForForm(first);

    }
    catch (TechnicalIOException tioe) {
      exception = tioe;
    }

  }



  /**
   * this method implement ImageItem from widget Image'gwt.
   * 
   * @param labelItem
   *          is of type LabelItem.
   */
  public void visitLabelItem(final LabelItem labelItem)
  {
    visitSimpleItem(labelItem);
  }



  /**
   * visit a {@link ButtonItem}.
   * 
   * @param buttonItem
   *          the {@link ButtonItem} to visit
   */
  public void visitButtonItem(ButtonItem buttonItem)
  {
    visitSimpleItem(buttonItem, ACTION_PROPERTY, URL_PROPERTY, SAVE_PROPERTY);
  }



  /**
   * Generate a PasswordBoxItem from a PasswordBox widget.
   * 
   * @param passwordBoxItem
   *          is of type passwordBox.
   */
  public void visitPasswordBoxItem(final PasswordBoxItem passwordBoxItem)
  {
    visitSimpleItem(passwordBoxItem);
  }



  /**
   * Generate a TextAreaItem from a TextArea widget.
   * 
   * @param textAreaItem
   *          is of type TextArea.
   */
  public void visitTextAreaItem(final TextAreaItem textAreaItem)
  {
    visitSimpleItem(textAreaItem);
  }



  /**
   * this method implement a RichTextAreaItem with RichTextArea from gwt.
   * 
   * @param richTextAreaItem
   *          an item.
   */
  public void visitRichTextAreaItem(final RichTextAreaItem richTextAreaItem)
  {
    visitSimpleItem(richTextAreaItem);
  }



  /**
   * handling {@link SimpleItem} with no other properties.
   * 
   * @param simpleItem
   *          the {@link SimpleItem} to display
   */
  private void visitSimpleItem(SimpleItem simpleItem, String... properties)
  {
    visitItem(simpleItem, appendProperties(properties, VALIDATOR_PROPERTY));
  }



  /**
   * handling {@link SimpleItem} with no other properties.
   * 
   * @param simpleItem
   *          the {@link SimpleItem} to display
   */
  private void visitItem(Item simpleItem, String... properties)
  {
    if (exception != null) {
      return;
    }
    try {
      final boolean first = this.first;
      prefixForItem(simpleItem, properties);
      suffixForForm(first);
    }
    catch (TechnicalIOException tioe) {
      exception = tioe;
    }
  }



  /**
   * handling {@link AbstractDateItem}.
   * 
   * @param abstractDateItem
   *          the {@link AbstractDateItem} to display
   */
  private void visitAbstractDateItem(AbstractDateItem abstractDateItem)
  {
    visitAbstractPeriodItem(abstractDateItem, DATE_FORMAT_PROPERTY);
  }



  /**
   * handling {@link AbstractPeriodItem}.
   * 
   * @param abstractPeriodItem
   *          the {@link AbstractPeriodItem} to display
   * @param properties
   *          the properties to retrieve from the bean
   */
  private void visitAbstractPeriodItem(AbstractPeriodItem abstractPeriodItem, String... properties)
  {
    visitSimpleItem(abstractPeriodItem, appendProperties(properties, NULLABLE_PROPERTY));
  }



  /**
   * generate textBoxItem from a textbox widget.
   * 
   * @param textBoxItem
   *          is of type TextBoxItem.
   */
  public void visitTextBoxItem(final TextBoxItem textBoxItem)
  {
    visitSimpleItem(textBoxItem);
  }



  /**
   * generate dateItem from a textbox widget.
   * 
   * @param dateItem
   *          is of type {@link DateItem}.
   */
  public void visitDateItem(DateItem dateItem)
  {
    visitAbstractDateItem(dateItem);
  }



  /**
   * generate timeItem from a textbox widget.
   * 
   * @param timeItem
   *          is of type {@link TimeItem}.
   */
  public void visitTimeItem(TimeItem timeItem)
  {
    visitAbstractPeriodItem(timeItem);
  }



  /**
   * generate dateTimeItem from a textbox widget.
   * 
   * @param dateTimeItem
   *          is of type {@link DateTimeItem}.
   */
  public void visitDateTimeItem(DateTimeItem dateTimeItem)
  {
    visitAbstractDateItem(dateTimeItem);
  }



  /**
   * visit {@link FileUploadItem}.
   * 
   * @param item
   *          the item to visit
   * @see com.genia.toolbox.web.gwt.form.client.visitor.FormVisitor#visitFileUploadItem(com.genia.toolbox.web.gwt.form.client.form.impl.FileUploadItemImpl)
   */
  public void visitFileUploadItem(FileUploadItem item)
  {
    visitSimpleItem(item);
  }



  /**
   * visit {@link MultipleFileUploadItem}.
   * 
   * @param item
   *          the item to visit
   * @see com.genia.toolbox.web.gwt.form.client.visitor.FormVisitor#visitFileUploadItem(com.genia.toolbox.web.gwt.form.client.form.impl.FileUploadItemImpl)
   */
  public void visitMultipleFileUploadItem(MultipleFileUploadItem item)
  {
    visitSimpleItem(item, SORTABLE_PROPERTY, SORT_BUTTONS_DISPOSITION_VERTICAL_PROPERTY);
  }



  /**
   * visit {@link VerticalForm}.
   * 
   * @param item
   *          the item to visit
   * @see {@link com.genia.toolbox.web.gwt.form.client.visitor.FormVisitor#visitVerticalForm(VerticalForm)}
   */
  public void visitVerticalForm(VerticalForm item)
  {

    if (exception != null) {
      return;
    }
    try {

      final boolean first = this.first;
      prefixForBasicForm(item);
      suffixForForm(first);
    }
    catch (TechnicalIOException tioe) {
      exception = tioe;
    }

  }



  /**
   * visit {@link EnumDescriptableValuedItemEntryList}.
   * 
   * @param valuedItemEntryList
   *          to visit
   */
  public void visitEnumDescriptableValuedItemEntryList(EnumDescriptableValuedItemEntryList valuedItemEntryList)
  {
    if (exception != null) {
      return;
    }

    try {

      final boolean first = this.first;
      prefixForObject(EnumDescriptableValuedItemEntryListFactory.class);
      appendProperty("enumClassName", valuedItemEntryList.getEnumClassName());
      suffixForForm(first);
    }
    catch (TechnicalIOException tioe) {
      exception = tioe;
    }
  }



  /**
   * visit {@link SimpleValuedItemEntryList}.
   * 
   * @param valuedItemEntryList
   *          to visit
   */
  @SuppressWarnings("unchecked")
  public void visitSimpleValuedItemEntryList(SimpleValuedItemEntryList valuedItemEntryList)
  {

    if (exception != null) {
      return;
    }

    try {

      final boolean first = this.first;
      prefixForObject(valuedItemEntryList);

      if (valuedItemEntryList.getValuedItemEntries() != null && !valuedItemEntryList.getValuedItemEntries().isEmpty()) {

        append("<property name='valuedItemEntries'><list>");

        for (ValuedItemEntry entry : valuedItemEntryList.getValuedItemEntries()) {

          append("<bean class='" + entry.getClass().getCanonicalName() + "'>");

          if (entry.getLabel() != null) {

            appendLabelProperty("label", entry.getLabel());

          }

          appendProperty("value", entry.getValue());

          append("</bean>");

        }

        append("</list></property>");

      }

      suffixForForm(first);

    }
    catch (TechnicalIOException tioe) {
      exception = tioe;
    }

  }



  /**
   * visit a {@link LabeledForm}.
   * 
   * @param labeledForm
   *          the {@link LabeledForm} to visit
   */
  public void visitLabeledForm(LabeledForm labeledForm)
  {

    if (exception != null) {
      return;
    }

    try {

      final boolean first = this.first;

      prefixForForm(labeledForm, "label");

      if (labeledForm.getSubForm() != null) {

        append("<property name='subForm'>");

        labeledForm.getSubForm().accept(this);

        append("</property>");

      }

      suffixForForm(first);

    }
    catch (TechnicalIOException tioe) {
      exception = tioe;
    }

  }



  /**
   * visit a {@link I18nTextAreaItem}.
   * 
   * @param i18nTextAreaItem
   *          the {@link I18nTextAreaItem} to visit
   */
  public void visitI18nTextAreaItem(final I18nTextAreaItem i18nTextAreaItem)
  {
    visitSimpleItem(i18nTextAreaItem);
  }



  /**
   * visit a {@link I18nTextBoxItem}.
   * 
   * @param item
   *          the {@link I18nTextBoxItem} to visit
   */
  public void visitI18nTextBoxItem(I18nTextBoxItem item)
  {
    visitSimpleItem(item);
  }

}
